package skiree.host.abetsy.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.jetbrains.annotations.NotNull;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import skiree.host.abetsy.api.service.global.GlobalDIYService;
import skiree.host.abetsy.dao.LocalAnimeMapper;
import skiree.host.abetsy.dao.LocalCommentMapper;
import skiree.host.abetsy.dao.LocalEpisodeMapper;
import skiree.host.abetsy.model.LocalAnime;
import skiree.host.abetsy.model.LocalComment;
import skiree.host.abetsy.model.LocalEpisode;
import skiree.host.abetsy.model.ddp.SearchEpisodes;
import skiree.host.abetsy.model.ddp.Episode;
import skiree.host.abetsy.model.ddp.SearchAnime;
import skiree.host.abetsy.service.LocalService;
import skiree.host.abetsy.service.util.DouBanUtil;

import javax.annotation.Resource;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Component
public class LocalServiceImpl implements LocalService {

    @Resource
    private LocalAnimeMapper localAnimeMapper;

    @Resource
    private LocalEpisodeMapper localEpisodeMapper;

    @Resource
    private GlobalDIYService globalDIYService;

    @Resource
    private LocalCommentMapper localCommentMapper;

    @Override
    public List<SearchEpisodes> searchByLocal(String anime, String episode) {
        List<SearchEpisodes> searchEpisodesList = new ArrayList<>();
        LocalAnime localAnime = handleAnimeName(anime);
        MPJLambdaWrapper<LocalEpisode> wrapper = JoinWrappers.lambda(LocalEpisode.class)
                .selectAll(LocalEpisode.class).leftJoin(LocalAnime.class, LocalAnime::getId, LocalEpisode::getAnimeId)
                .eq(LocalAnime::getTitle, localAnime.getTitle()).eq(LocalAnime::getSeason, localAnime.getSeason())
                .eq(LocalEpisode::getEpisodeIndex, Integer.parseInt(episode))
                .eq(LocalEpisode::getStatus, "loaded");
        LocalEpisode localEpisode = localEpisodeMapper.selectJoinList(LocalEpisode.class, wrapper).stream().findFirst().orElse(null);
        if (localEpisode != null) {
            LocalAnime localAnimeDb = localAnimeMapper.selectById(localEpisode.getAnimeId());
            SearchEpisodes searchEpisodesData = getAnime(localAnimeDb, localEpisode);
            searchEpisodesList.add(searchEpisodesData);
        }
        return searchEpisodesList;
    }

    @Override
    public List<SearchAnime> searchAnimeByLocal(String keyword) {
        List<SearchAnime> animeList = new ArrayList<>();
        LocalAnime localAnime = handleAnimeName(keyword);
        // 先检查此剧之前是否存在
        QueryWrapper<LocalAnime> animeQuery = new QueryWrapper<>();
        animeQuery.eq("title", localAnime.getTitle());
        animeQuery.eq("season", localAnime.getSeason());
        List<LocalAnime> localAnimeDbs = localAnimeMapper.selectList(animeQuery);
        if (!localAnimeDbs.isEmpty()) {
            for (LocalAnime animeDb : localAnimeDbs) {
                SearchAnime searchAnime = new SearchAnime();
                searchAnime.setAnimeId(animeDb.getId());
                searchAnime.setBangumiId(animeDb.getId().toString());
                // 重新设置标题名
                searchAnime.setAnimeTitle(animeDb.getTitle() + " 第" + Convert.numberToChinese(animeDb.getSeason(), false) + "季");
                searchAnime.setType("web");
                searchAnime.setTypeDescription("ABetsy 弹幕源");
                searchAnime.setImageUrl(animeDb.getImg().replace("jpg", "webp"));
                searchAnime.setStartDate(DateUtil.format(animeDb.getTime(), "yyyy-MM-dd HH:mm:ss"));
                // 设置子集数
                QueryWrapper<LocalEpisode> queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("anime_id", animeDb.getId());
                queryWrapper.eq("status", "loaded");
                Long localEpisodesNum = localEpisodeMapper.selectCount(queryWrapper);
                searchAnime.setEpisodeCount(localEpisodesNum);
                searchAnime.setRating(5.00);
                searchAnime.setFavorited(false);
                animeList.add(searchAnime);
            }
        }
        return animeList;
    }

    @Override
    public List<SearchEpisodes> onSearchByLocal(String anime) {
        List<SearchEpisodes> searchEpisodesList = new ArrayList<>();
        LocalAnime localAnime = handleAnimeName(anime);
        MPJLambdaWrapper<LocalEpisode> wrapper = JoinWrappers.lambda(LocalEpisode.class)
                .selectAll(LocalEpisode.class).leftJoin(LocalAnime.class, LocalAnime::getId, LocalEpisode::getAnimeId)
                .eq(LocalAnime::getTitle, localAnime.getTitle())
                .eq(LocalAnime::getSeason, localAnime.getSeason())
                .eq(LocalEpisode::getStatus, "loaded");
        List<LocalEpisode> localEpisodeList = localEpisodeMapper.selectJoinList(LocalEpisode.class, wrapper);
        if (!localEpisodeList.isEmpty()) {
            LocalAnime localAnimeDb = localAnimeMapper.selectById(localEpisodeList.get(0).getAnimeId());
            localEpisodeList = localEpisodeList.stream()
                    .sorted(Comparator.comparingInt(LocalEpisode::getEpisodeIndex))
                    .collect(Collectors.toList());
            for (LocalEpisode episode : localEpisodeList) {
                SearchEpisodes searchEpisodesData = getAnime(localAnimeDb, episode);
                searchEpisodesList.add(searchEpisodesData);
            }
        }
        return searchEpisodesList;
    }

    @Override
    public void handleUnknownAnime(String anime, String episodeNum) {
        LocalAnime localAnime = handleAnimeName(anime);
        // 先检查此剧之前是否存在
        QueryWrapper<LocalAnime> animeQuery = new QueryWrapper<>();
        animeQuery.eq("title", localAnime.getTitle());
        animeQuery.eq("season", localAnime.getSeason());
        LocalAnime localAnimeDb = localAnimeMapper.selectOne(animeQuery);
        if (localAnimeDb != null) {
            localAnime = localAnimeDb;
        } else {
            // 收集豆瓣剧集
            DouBanUtil.douBanLink(localAnime);
            localAnimeMapper.insert(localAnime);
        }
        // 记录剧集每集
        List<LocalEpisode> localEpisodes = recordEpisode(localAnime);
        // 处理不需要的集数
        if (localAnimeDb != null) {
            // 存在历史数据
            localEpisodes = localEpisodes.stream().filter(x -> x.getEpisodeIndex().equals(Integer.parseInt(episodeNum))).collect(Collectors.toList());
        }
        for (LocalEpisode episode : localEpisodes) {
            // 先检查此集是否已经存在
            QueryWrapper<LocalEpisode> episodeQuery = new QueryWrapper<>();
            episodeQuery.eq("anime_id", localAnime.getId());
            episodeQuery.eq("episode_index", episode.getEpisodeIndex());
            LocalEpisode selectEpisode = localEpisodeMapper.selectOne(episodeQuery);
            if (selectEpisode == null) {
                localEpisodeMapper.insert(episode);
            } else {
                episode = selectEpisode;
            }
            globalDIYService.loadDanMu(episode);
        }
    }

    @Override
    public List<LocalComment> commentByLocal(String episodeId) {
        List<LocalComment> comments;
        MPJLambdaWrapper<LocalComment> wrapper = JoinWrappers.lambda(LocalComment.class)
                .selectAll(LocalComment.class).leftJoin(LocalEpisode.class, LocalEpisode::getId, LocalComment::getEpisodeId)
                .eq(LocalComment::getEpisodeId, Integer.parseInt(episodeId))
                .eq(LocalEpisode::getStatus, "loaded");
        comments = localCommentMapper.selectJoinList(LocalComment.class, wrapper);
        // todo 这块不需要自动化加载弹幕了 必须在控制台操作
        if (comments.isEmpty()) {
            /*
               现在有多种情况
              1. init 刚刚初始化 (无需处理)
              2. loading 处理弹幕中 (无需处理)
              3. error 上一次处理异常 (需再次尝试处理)
             */
            localLoadDanMu(Integer.parseInt(episodeId));
        }
        return comments;
    }

    @Async
    protected void localLoadDanMu(int episodeId) {
        LocalEpisode localEpisode = localEpisodeMapper.selectById(episodeId);
        if (localEpisode.getStatus().equals("error")) {
            globalDIYService.loadDanMu(localEpisode);
        }
    }

    private static @NotNull SearchEpisodes getAnime(LocalAnime localAnimeDb, LocalEpisode localEpisodeDb) {
        SearchEpisodes searchEpisodesData = new SearchEpisodes();
        searchEpisodesData.setAnimeId(localAnimeDb.getId());
        searchEpisodesData.setAnimeTitle(localAnimeDb.getTitle() + " 第" + localAnimeDb.getSeason() + "季");
        searchEpisodesData.setType("web");
        searchEpisodesData.setTypeDescription("网络放送");

        Episode episodeData = new Episode();
        episodeData.setEpisodeId(localEpisodeDb.getId());
        episodeData.setEpisodeTitle(localEpisodeDb.getTitle());

        searchEpisodesData.setEpisodes(Collections.singletonList(episodeData));
        return searchEpisodesData;
    }

    private List<LocalEpisode> recordEpisode(LocalAnime localAnime) {
        List<LocalEpisode> episodeList = new ArrayList<>();
        String result = HttpUtil.get(localAnime.getUrl(), CharsetUtil.CHARSET_UTF_8);
        Matcher matcher = Pattern.compile("\\{play_link(.*?)}").matcher(result);
        while (matcher.find()) {
            JSONObject link = JSONUtil.parseObj(matcher.group());
            Integer ep = Integer.parseInt(link.getStr("ep"));
            String playLink = link.getStr("play_link");
            playLink = URLDecoder.decode(playLink, CharsetUtil.CHARSET_UTF_8);
            playLink = playLink.replace("https://www.douban.com/link2/?url=", "");
            playLink = playLink.split(".html")[0] + ".html";
            playLink = playLink.replace("http:", "https:");
            if (playLink.contains("?bsource=doubanh5")) {
                playLink = playLink.split("\\?bsource=doubanh5")[0];
            }
            LocalEpisode episode = new LocalEpisode();
            episode.setAnimeId(localAnime.getId());
            episode.setTitle("第" + ep + "集");
            episode.setEpisodeIndex(ep);
            episode.setUrl(playLink);
            episode.setStatus("init");
            episode.setTime(new Date());
            episodeList.add(episode);
        }
        return episodeList;
    }

    @Override
    public LocalAnime handleAnimeName(String anime) {
        LocalAnime localAnime = new LocalAnime();
        if (anime.contains(" ")) {
            localAnime.setTitle(anime.split(" ")[0]);
            Matcher matcher = Pattern.compile("([0-9]+)").matcher(anime.split(" ")[1]);
            if (matcher.find()) {
                localAnime.setSeason(Integer.parseInt(matcher.group(0)));
            }
        } else {
            localAnime.setTitle(anime);
            localAnime.setSeason(1);
        }
        localAnime.setTime(new Date());
        return localAnime;
    }

}
